﻿//***************************************************************************
// Contributed by Ondrej Dvorak, 2013: Changes with respect to the DSLPed 
// project
//
// Copyright © 2010 Microsoft Corporation.  All Rights Reserved.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
//***************************************************************************
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.OLE.Interop;

using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;

namespace Dsl.Ped.Designer
{
    /// <summary>
    /// This is the factory that creates an editor object. Must extend <see cref="IVsEditorFactory"/>
    /// </summary>
    [Guid(Guids.guidDslPedDesignerEditorFactoryString)]
    public sealed class DSLPedEditorFactory : IVsEditorFactory, IDisposable
    {
        /// <summary>
        /// This is the extension which is recognized by a DSLPed designer.
        /// Any file of this extension will be automatically opened in DSLPed editor
        /// </summary>
        public const string Extension = ".dsl";

        /// <summary>
        /// Implementation of VS package for the DSLPed designer
        /// </summary>
        private readonly DslPedDesignerPackage editorPackage;

        /// <summary>
        /// Unified service provider for managing the <see cref="editorPackage"/>.
        /// <see cref="ServiceProvider"/> class is a manager class of VSPackages
        /// </summary>
        private ServiceProvider serviceProvider;

        /// <summary>
        /// Initializes a new instance of the <see cref="DSLPedEditorFactory"/> class.
        /// </summary>
        /// <param name="package">The package of the DSLPed designer</param>
        public DSLPedEditorFactory(DslPedDesignerPackage package)
        {
            // Set the editor package to the given one
            this.editorPackage = package;
        }

        #region IVsEditorFactory Members

        /// <summary>
        /// Inicialization of an editor in the environment
        /// </summary>
        /// <param name="psp">pointer to the <see cref="Microsoft.VisualStudio.OLE.Interop..IServiceProvider"/>. 
        /// Can be used to obtain instances of other interfaces
        /// </param>
        /// <returns><see cref="VSConstants.S_OK"/> if a method succeeds, error code otherwise</returns>
        public int SetSite(Microsoft.VisualStudio.OLE.Interop.IServiceProvider psp)
        {
            this.serviceProvider = new ServiceProvider(psp);
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Release all cached interface pointers and unregisters any event sink
        /// </summary>
        /// <returns><see cref="VSConstants.S_OK"/> if a method succeeds, error code otherwise</returns>
        public int Close()
        {
            return VSConstants.S_OK;
        }

        /// <summary>
        /// Gets type-based service from unmanaged <see cref="ServiceProvider"/>
        /// </summary>
        /// <param name="serviceType">The type of service to retrieve. The GUID of this type is used to obtain the service from the
        /// native service</param>
        /// <returns>The requested service, or a null reference if the service could not be located</returns>
        public object GetService(Type serviceType)
        {
            return this.serviceProvider.GetService(serviceType);
        }

        // This method is called by the Environment (inside IVsUIShellOpenDocument::
        // OpenStandardEditor and OpenSpecificEditor) to map a LOGICAL view to a 
        // PHYSICAL view. A LOGICAL view identifies the purpose of the view that is
        // desired (e.g. a view appropriate for Debugging [LOGVIEWID_Debugging], or a 
        // view appropriate for text view manipulation as by navigating to a find
        // result [LOGVIEWID_TextView]). A PHYSICAL view identifies an actual type 
        // of view implementation that an IVsEditorFactory can create. 
        //
        // NOTE: Physical views are identified by a string of your choice with the 
        // one constraint that the default/primary physical view for an editor  
        // *MUST* use a NULL string as its physical view name (*pbstrPhysicalView = NULL).
        //
        // NOTE: It is essential that the implementation of MapLogicalView properly
        // validates that the LogicalView desired is actually supported by the editor.
        // If an unsupported LogicalView is requested then E_NOTIMPL must be returned.
        //
        // NOTE: The special Logical Views supported by an DslPedEditor Factory must also 
        // be registered in the local registry hive. LOGVIEWID_Primary is implicitly 
        // supported by all editor types and does not need to be registered.
        // For example, an editor that supports a ViewCode/ViewDesigner scenario
        // might register something like the following:
        //        HKLM\Software\Microsoft\VisualStudio\<version>\Editors\
        //            {...guidEditor...}\
        //                LogicalViews\
        //                    {...LOGVIEWID_TextView...} = s ''
        //                    {...LOGVIEWID_Code...} = s ''
        //                    {...LOGVIEWID_Debugging...} = s ''
        //                    {...LOGVIEWID_Designer...} = s 'Form'
        //
        public int MapLogicalView(ref Guid rguidLogicalView, out string pbstrPhysicalView)
        {
            // initialize out parameter
            pbstrPhysicalView = null;    

            // we support only a single physical view
            if (VSConstants.LOGVIEWID_Primary == rguidLogicalView)
            {
                // primary view uses NULL as pbstrPhysicalView
                return VSConstants.S_OK; 
            }

            // must return E_NOTIMPL for any unrecognized rguidLogicalView values
            return VSConstants.E_NOTIMPL;   
        }

        /// <summary>
        /// Used by the editor factory to create an editor instance. the environment first determines the 
        /// editor factory with the highest priority for opening the file and then calls 
        /// IVsEditorFactory.CreateEditorInstance. If the environment is unable to instantiate the document data 
        /// in that editor, it will find the editor with the next highest priority and attempt to so that same 
        /// thing. 
        /// NOTE: The priority of our editor is 32 as mentioned in the attributes on the package class.
        /// 
        /// Since our editor supports opening only a single view for an instance of the document data, if we 
        /// are requested to open document data that is already instantiated in another editor, or even our 
        /// editor, we return a value VS_E_INCOMPATIBLEDOCDATA.
        /// </summary>
        /// <param name="grfCreateDoc">Flags determining when to create the editor. Only open and silent flags 
        /// are valid
        /// </param>
        /// <param name="pszMkDocument">path to the file to be opened</param>
        /// <param name="pszPhysicalView">name of the physical view</param>
        /// <param name="pvHier">pointer to the IVsHierarchy interface</param>
        /// <param name="itemid">Item identifier of this editor instance</param>
        /// <param name="punkDocDataExisting">This parameter is used to determine if a document buffer 
        /// (DocData object) has already been created
        /// </param>
        /// <param name="ppunkDocView">Pointer to the IUnknown interface for the DocView object</param>
        /// <param name="ppunkDocData">Pointer to the IUnknown interface for the DocData object</param>
        /// <param name="pbstrEditorCaption">Caption mentioned by the editor for the doc window</param>
        /// <param name="pguidCmdUI">the Command UI Guid. Any UI element that is visible in the editor has 
        /// to use this GUID. This is specified in the .vsct file
        /// </param>
        /// <param name="pgrfCDW">Flags for CreateDocumentWindow</param>
        /// <returns></returns>
        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        public int CreateEditorInstance(
                        uint grfCreateDoc,
                        string pszMkDocument,
                        string pszPhysicalView,
                        IVsHierarchy pvHier,
                        uint itemid,
                        System.IntPtr punkDocDataExisting,
                        out System.IntPtr ppunkDocView,
                        out System.IntPtr ppunkDocData,
                        out string pbstrEditorCaption,
                        out Guid pguidCmdUI,
                        out int pgrfCDW)
        {
            // Initialize to null
            ppunkDocView = IntPtr.Zero;
            ppunkDocData = IntPtr.Zero;
            pguidCmdUI = Guids.guidDslPedDesignerEditorFactory;
            pgrfCDW = 0;
            pbstrEditorCaption = null;

            // Validate inputs
            if ((grfCreateDoc & (VSConstants.CEF_OPENFILE | VSConstants.CEF_SILENT)) == 0)
            {
                return VSConstants.E_INVALIDARG;
            }

            IVsTextLines textBuffer = null;

            if (punkDocDataExisting == IntPtr.Zero)
            {
                // punkDocDataExisting is null which means the file is not yet open.
                // We need to create a new text buffer object 

                // get the ILocalRegistry interface so we can use it to
                // create the text buffer from the shell's local registry
                try
                {
                    ILocalRegistry localRegistry = (ILocalRegistry)GetService(typeof(SLocalRegistry));
                    if (localRegistry != null)
                    {
                        IntPtr ptr;
                        Guid iid = typeof(IVsTextLines).GUID;
                        Guid CLSID_VsTextBuffer = typeof(VsTextBufferClass).GUID;
                        localRegistry.CreateInstance(CLSID_VsTextBuffer, null, ref iid, 1 /*CLSCTX_INPROC_SERVER*/, out ptr);
                        try
                        {
                            textBuffer = Marshal.GetObjectForIUnknown(ptr) as IVsTextLines;
                        }
                        finally
                        {
                            Marshal.Release(ptr); // Release RefCount from CreateInstance call
                        }

                        // It is important to site the TextBuffer object
                        IObjectWithSite objWSite = (IObjectWithSite)textBuffer;
                        if (objWSite != null)
                        {
                            Microsoft.VisualStudio.OLE.Interop.IServiceProvider oleServiceProvider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)GetService(typeof(Microsoft.VisualStudio.OLE.Interop.IServiceProvider));
                            objWSite.SetSite(oleServiceProvider);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("Can not get IVsCfgProviderEventsHelper" + ex.Message);
                    throw;
                }
            }
            else
            {
                // punkDocDataExisting is *not* null which means the file *is* already open. 
                // We need to verify that the open document is in fact a TextBuffer. If not
                // then we need to return the special error code VS_E_INCOMPATIBLEDOCDATA which
                // causes the user to be prompted to close the open file. If the user closes the
                // file then we will be called again with punkDocDataExisting as null

                // QI existing buffer for text lines
                textBuffer = Marshal.GetObjectForIUnknown(punkDocDataExisting) as IVsTextLines;
                if (textBuffer == null)
                {
                    return VSConstants.VS_E_INCOMPATIBLEDOCDATA;
                }
            }

            // Create the Document (editor)
            DSLPedEditorPane NewEditor = new DSLPedEditorPane(editorPackage, pszMkDocument, textBuffer);
            ppunkDocView = Marshal.GetIUnknownForObject(NewEditor);
            ppunkDocData = Marshal.GetIUnknownForObject(textBuffer);
            pbstrEditorCaption = "";
            return VSConstants.S_OK;
        }

        #endregion

        #region IDisposable Members
     
        /// <summary>
        /// Performs applicatoin-defined tasks associated with freeing, releasing, or resetting unmanaged resources
        /// </summary>
        public void Dispose()
        {
            // Call dispose, the direct call was done, the parameter must be set true
            this.Dispose(true);
        }

        /// <summary>
        /// This method performs instance resources clean up
        /// </summary>
        /// <param name="disposing">This parameter determines whether the method has been called directly or indirectly by a user's code</param>
        private void Dispose(bool disposing)
        {
            lock (this)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources
                if (disposing)
                {
                    if (this.serviceProvider != null)
                    {
                        this.serviceProvider.Dispose();
                        this.serviceProvider = null;
                    }
                }
            }
        }
        #endregion
    }
}
